#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using Agro2DPipeline;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Agro2D
{
    /// <summary>
    /// Attribute that can be applied to Properties that can be loaded from xml data.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class ImportAttribute : Attribute
    {
    }

    /// <summary>
    /// Various static methods to convert Sprite info structures to Sprites,
    /// SpriteProperties, CollisionProperties, and Animations
    /// </summary>
    public class SpriteLoader
    {
        #region Fields
        static GraphicsDevice graphicsDevice;
        #endregion

        static public void Initialize(GraphicsDevice graphicsDev)
        {
            graphicsDevice = graphicsDev;
        }

        static public GraphicsDevice GraphicsDevice
        {
            get { return graphicsDevice; }
        }

        /// <summary>
        ///  Loads the data neccessary for a Sprite (template).
        /// </summary>
        /// <param name="info">SpriteInfo structure with sprite information</param>
        /// <param name="spriteManager">SpriteManager to pass to sprite template</param>
        /// <returns></returns>
        static public Sprite LoadSpriteTemplate(SpriteInfo info, SpriteManager spriteManager, Assembly myAssembly)
        {
            Sprite newSprite = CreateSpriteTemplate(info.TypeName, spriteManager, myAssembly);
            ApplyStandardProperties(info, newSprite);
            LoadSpriteProperties(info, newSprite);
            LoadCollisionProperties(info, newSprite);

            return newSprite;

        }

        /// <summary>
        /// Parses SpriteInfo structure and loads a SpriteProperties object.
        /// </summary>
        /// <param name="info">SpriteInfo structure</param>
        /// <returns>Sprite Properties</returns>
        static public void LoadSpriteProperties(SpriteInfo info, Sprite sprite)
        {
            object[] attributes;
            object value;
            if(sprite.SpriteProperties == null)
                sprite.SpriteProperties = new SpriteProperties();

            foreach(PropertyInfo prop in sprite.SpriteProperties.GetType().GetProperties())
            {
                attributes = prop.GetCustomAttributes(typeof(ImportAttribute), true);
                if(attributes.Length > 0)
                {
                    if(info.SpriteProperties.ContainsKey(prop.Name))
                    {
                        value = ParseHelper.ParseValue(prop, info.SpriteProperties[prop.Name]);
                        ParseHelper.TestConvertedValue(value, typeof(SpriteProperties), prop, info.SpriteProperties[prop.Name]);

                        // Apply property
                        prop.SetValue(sprite.SpriteProperties, value, null);
                    }
                }                
            }
        }

        /// <summary>
        /// Parses SpriteInfo structure and loads a CollisionProperties object.
        /// </summary>
        /// <param name="info">SpriteInfo structure</param>
        /// <returns>Collision Properties</returns>
        static public void LoadCollisionProperties(SpriteInfo info, Sprite sprite)
        {
            object[] attributes;
            object value;
            if(sprite.CollisionProperties == null)
                sprite.CollisionProperties = new CollisionProperties();

            foreach(PropertyInfo prop in sprite.CollisionProperties.GetType().GetProperties())
            {
                attributes = prop.GetCustomAttributes(typeof(ImportAttribute), true);
                if(attributes.Length > 0)
                {
                    if(info.CollisionProperties.ContainsKey(prop.Name))
                    {
                        value = ParseHelper.ParseValue(prop, info.CollisionProperties[prop.Name]);
                        ParseHelper.TestConvertedValue(value, typeof(CollisionProperties), prop, info.CollisionProperties[prop.Name]);

                        // Apply property
                        prop.SetValue(sprite.CollisionProperties, value, null);
                    }
                }                
            }
        }

        /// <summary>
        /// Loads the C# properties of a Sprite with the loaded information from the SpriteInfo
        /// </summary>
        /// <param name="info">SpriteInfo structure</param>
        /// <param name="sprite">The Sprite that should have its Properties set</param>
        static public void ApplyStandardProperties(SpriteInfo info, Sprite sprite)
        {
            object[] attributes;
            object value;

            foreach(PropertyInfo prop in sprite.GetType().GetProperties())
            {
                attributes = prop.GetCustomAttributes(typeof(ImportAttribute), true);
                if(attributes.Length > 0)
                {
                    if(info.Properties.ContainsKey(prop.Name))
                    {
                        value = ParseHelper.ParseValue(prop, info.Properties[prop.Name]);
                        ParseHelper.TestConvertedValue(value, typeof(Sprite), prop, info.Properties[prop.Name]);

                        // Apply property
                        prop.SetValue(sprite, value, null);
                    }
                }                
            }
        }

        /// <summary>
        /// Loads animations from the AnimationInfo structure.
        /// </summary>
        /// <param name="info">AnimationInfo structure</param>
        /// <returns>Concreate animation class with the loaded animations</returns>
        public static IAnimation LoadAnimations(AnimationInfo parentInfo, Assembly myAssembly)
        {
            object value;
            object[] attributes;
            if(parentInfo.TypeName == "AnimationContainer")
            {
                string containerType = parentInfo.Properties["type"];
                IAnimationContainer container = (IAnimationContainer)CreateSpriteAnimation(containerType, myAssembly);

                foreach(AnimationInfo animInfo in parentInfo.NestedAnimations)
                {
                    AnimationInfo localInfo = animInfo;
                    IAnimation animation = LoadAnimations(localInfo, myAssembly);
                    // ActionAnimation.
                    if(Type.GetType(containerType).IsSubclassOf(Type.GetType("Agro2D.ActionAnimation"))
                        || containerType == "Agro2D.ActionAnimation")
                    {
                        Debug.Assert(animInfo.Properties.ContainsKey("ActionName"), 
                            "Animations have an ActionAnimation container, but"
                            + " animation has no ActionName attribute");
                        try
                        {
                            ((ActionAnimation)container).AddAction((Action)animation);
                        }
                        catch(Exception e)
                        {
                            Log.Instance.LogMessage(e.Message + ". Loading aborted.");
                            return null;
                        }
                    }
                    else
                        container.Animations.Add(animation);
                    
                }

                return (IAnimation)container;
            }
            else if(parentInfo.TypeName == "Action")
            {
                Debug.Assert(parentInfo.NestedAnimations.Count > 0, "No animations added to Action");
                AnimationInfo localInfo = parentInfo.NestedAnimations[0];
                IAnimation animation = LoadAnimations(localInfo, myAssembly);
                Action action = new Action(parentInfo.Properties["ActionName"], animation);
                return (IAnimation)action;
            }
            else
            {
                string type = parentInfo.TypeName;
                IAnimation animation = (IAnimation)CreateSpriteAnimation(type, myAssembly);

                // Animation properties.
                foreach(PropertyInfo prop in animation.GetType().GetProperties())
                {
                    attributes = prop.GetCustomAttributes(typeof(ImportAttribute), true);
                    if(attributes.Length > 0)
                    {
                        if(parentInfo.Properties.ContainsKey(prop.Name))
                        {
                            value = ParseHelper.ParseValue(prop, parentInfo.Properties[prop.Name]);
                            ParseHelper.TestConvertedValue(value, typeof(IAnimation), prop, parentInfo.Properties[prop.Name]);

                            // Apply property
                            prop.SetValue(animation, value, null);
                        }
                    }                    
                }

                // FrameAnimation.
                if (animation.GetType().IsSubclassOf(Type.GetType("Agro2D.FrameAnimation"))
                    || parentInfo.TypeName == "Agro2D.FrameAnimation")
                {
                    try
                    {
                        PrepareFrameAnim(parentInfo, (FrameAnimation)animation);
                    }
                    catch(Exception e)
                    {
                        Log.Instance.LogMessage(e.Message + ". Loading aborted.");
                        return null;
                    }
                }

                return animation;
            }

        }

        /// <summary>
        /// Load frame information and pass it to FrameAnimation.GenerateSourceRectangles
        /// </summary>
        /// <param name="animInfo">AnimationInfo structure</param>
        /// <param name="animation">FrameAnimation to apply information</param>
        static protected void PrepareFrameAnim(AnimationInfo animInfo, FrameAnimation animation)
        {
            uint width = uint.Parse(animInfo.Properties["frameWidth"]);
            uint height = uint.Parse(animInfo.Properties["frameHeight"]);
            uint numFrames = uint.Parse(animInfo.Properties["numframes"]);
            uint numColumns = uint.Parse(animInfo.Properties["numColumns"]);
            animation.GenerateSourceRectangles(animation.Texture, width, height,
                 numFrames, numColumns);
        }

        /// <summary>
        /// Creates an Sprite animation instance. Can create Standard Agro animations, and also
        /// custom animations as long as they are in the executing or calling assembly.
        /// </summary>
        /// <param name="qualifiedAnimation"></param>
        /// <returns></returns>
        static protected IAnimation CreateSpriteAnimation(string qualifiedAnimation, Assembly myAssembly)
        {

#if NULL
            Type animationType = null;
            if(qualifiedAnimation.StartsWith("Agro2D."))
                animationType = Type.GetType(qualifiedAnimation);
            else
            {
                // Load from assembly.            
                // NB: 'GetEntryAssembly'- entry assembly must implement named Sprite.
                // 'GetCallingAssembly' - in which the calling assembly must contain the sprite (ie. child class).
                try
                {
                    animationType = Assembly.GetEntryAssembly().GetType(qualifiedAnimation); // <-
                }
                catch
                {
                    animationType = Assembly.GetCallingAssembly().GetType(qualifiedAnimation); // <- 
                }
            }

            if(animationType == null && myAssembly != null)
            {
                try
                {
                    animationType = myAssembly.GetType(qualifiedAnimation);
                }
                catch
                {
                }
            }

            bool valid = (animationType != null);
            Debug.Assert(valid, "Invalid type name:" + qualifiedAnimation + ". Use Namespace.MyAnimation.");
            if(valid)
                return (IAnimation)Activator.CreateInstance(animationType, null);
            else
                return null;
#endif

            string nameSpace = qualifiedAnimation.Substring(0, qualifiedAnimation.IndexOf('.'));
            int lastindex = qualifiedAnimation.LastIndexOf('.');
            string typeName = qualifiedAnimation.Substring(lastindex + 1);

            return (IAnimation)AnimationFactory.Instance.CreateAnimation(typeName);

        }

        static public Type ResolveType(string qualifiedSprite)
        {
#if WINDOWS
            string nameSpace = qualifiedSprite.Substring(0, qualifiedSprite.IndexOf('.'));
            int lastindex = qualifiedSprite.LastIndexOf('.');
            string typeName = qualifiedSprite.Substring(lastindex+1);
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach(Assembly assembly in assemblies)
            {
                Type[] types = assembly.GetTypes();
                foreach(Type type in types)
                {
                    if(type.Name == typeName || type.Name ==  qualifiedSprite)
                    {
                        // return assembly from AppDomain
                        return type;
                    }
                }
                              
            }
            return null;
#endif
#if XBOX360
            return null;
#endif
        }

        /// <summary>
        /// Create a sprite. When not loading a standard Sprite, CreateSprite will attempt to 
        /// create a class from the current assembly. You must pass Namespace.MySprite.
        /// Note, currently that Sprite must be in entry assembly or calling assembly (ie. child class).
        /// </summary>
        /// <returns>Custom Sprite or null if not found</returns>
        static protected Sprite CreateSpriteTemplate(string qualifiedSprite, SpriteManager spriteManager, Assembly myAssembly)
        {
#if NULL
            // Is standard sprite? If not load from assembly.
            if(qualifiedSprite == "Agro2D.Sprite")
                return new Sprite(spriteManager);
            else
            {
                Type spriteType = null;
                // NB: 'GetEntryAssembly'- entry assembly must implement named Sprite.
                // 'GetCallingAssembly' - in which the calling assembly must contain the sprite (ie. child class).
                try
                {
                    spriteType = Assembly.GetEntryAssembly().GetType(qualifiedSprite); // <-
                }
                catch
                {
                    spriteType = Assembly.GetCallingAssembly().GetType(qualifiedSprite); // <-                    
                }

                //if(spriteType == null)
                    //spriteType = ResolveType(qualifiedSprite);    

                if(spriteType == null && myAssembly != null)
                {
                    try
                    {
                        spriteType = myAssembly.GetType(qualifiedSprite);
                    }
                    catch
                    {
                    }
                }

                bool valid = (spriteType != null);
                Debug.Assert(valid, "Invalid type name:" + qualifiedSprite + ". Use Namespace.MySprite.");
                if(valid)
                    return (Sprite)Activator.CreateInstance(spriteType, new object[] { spriteManager });
                else
                    return null;
            }
#endif

            string nameSpace = qualifiedSprite.Substring(0, qualifiedSprite.IndexOf('.'));
            int lastindex = qualifiedSprite.LastIndexOf('.');
            string typeName = qualifiedSprite.Substring(lastindex + 1);

            Sprite sprite = (Sprite)SpriteFactory.Instance.CreateSpriteInstance(typeName, spriteManager);
            sprite.SpriteManager = spriteManager;
            return sprite;
        }

        /// <summary>
        /// Read sprites from the xnb data provided by ContentReader and loads it into
        /// a SpriteTemplateInfo structure
        /// </summary>
        /// <param name="input">XNA ContentReader</param>
        /// <param name="instance">SpriteTemplateInfo structure to load</param>
        static public void ReadSprites(ContentReader input, SpriteTemplateInfo instance)
        {
            // Check how many Sprites we have defined.
            int spriteCount = input.ReadInt32();
            for(int i = 0; i < spriteCount; i++)
            {
                SpriteInfo spriteInfo = new SpriteInfo();
                spriteInfo.TypeName = input.ReadString();

                int standardPropsCountInt = input.ReadInt32();
                for(int a = 0; a < standardPropsCountInt; a++)
                {
                    string key = input.ReadString();
                    string value = input.ReadString();
                    spriteInfo.Properties.Add(key, value);
                }

                int spritePropsCountInt = input.ReadInt32();
                for(int j = 0; j < spritePropsCountInt; j++)
                {
                    string key = input.ReadString();
                    string value = input.ReadString();
                    spriteInfo.SpriteProperties.Add(key, value);
                }

                int collPropsCountInt = input.ReadInt32();
                for(int k = 0; k < collPropsCountInt; k++)
                {
                    string key = input.ReadString();
                    string value = input.ReadString();
                    spriteInfo.CollisionProperties.Add(key, value);
                }

                // Animations.
                spriteInfo.HasAnimation = input.ReadBoolean();
                if(spriteInfo.HasAnimation)
                {
                    spriteInfo.Animation = new AnimationInfo();
                    ReadAnimations(input, spriteInfo.Animation);
                }

                instance.Sprites.Add(spriteInfo); // Add sprite to list.
            }
        }


        static public void ReadAnimations(ContentReader input, AnimationInfo animInfo)
        {            
            animInfo.TypeName = input.ReadString();

            int animInfoPropsCountInt = input.ReadInt32();
            for(int a = 0; a < animInfoPropsCountInt; a++)
            {
                string key = input.ReadString();
                string value = input.ReadString();
                animInfo.Properties.Add(key, value);
            }

            int nestedAnimCount = input.ReadInt32();
            for(int m = 0; m < nestedAnimCount; m++)
            {
                AnimationInfo nestedAnimInfo = new AnimationInfo();
                ReadAnimations(input, nestedAnimInfo);
                animInfo.NestedAnimations.Add(nestedAnimInfo);
            } 
       
        }

        static public void ReadSpawnPoints(ContentReader input, List<SpawnPointInfo> spawnPointInfoList)
        {
            string spawnPointsHeader = input.ReadString();

            int spawnPointCountInt = input.ReadInt32();
            for(int a = 0; a < spawnPointCountInt; a++)
            {
                string spawnPointHeader = input.ReadString();
                SpawnPointInfo spawnInfo = new SpawnPointInfo();

                int spawnInfoPropertyCountInt = input.ReadInt32();
                for(int m = 0; m < spawnInfoPropertyCountInt; m++)
                {
                    string key = input.ReadString();
                    string value = input.ReadString();

                    spawnInfo.Properties.Add(key, value);
                }
                spawnPointInfoList.Add(spawnInfo);
            }                      

        }

    }    
}
